package hwip.processing;

import java.net.Socket;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.osgi.framework.BundleContext;
import org.osgi.util.tracker.ServiceTracker;

import android.content.Context;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.util.Log;
import de.hmm.hwip.configuration.api.ISystemConf;
import de.hmm.hwip.connection.api.IConOut;
import de.hmm.hwip.general.IAndroidContext;
import de.hmm.hwip.general.IMessageContent;
import de.hmm.hwip.storage.api.ISQLite;

public class Sys_Configuration implements IMessageContent {
	/** Class identifer to debug **/
	final String TAG = "System Config Messagehandler";
	
	/**
	 * security flag
	 */
	boolean securityFlag = false;
	
	/**
	 * variable needed for service tracker 
	 */
	private BundleContext context;
	
	/**
	 * spot config service tracker
	 */
	private ServiceTracker SysConfServiceTracker;
	
	/**
	 * database service tracker
	 */
	private ServiceTracker serviceTrackerDB;
	
	/**
	 * OutConnection service tracker
	 */
	private ServiceTracker serviceTrackerOutCon;
	
	/**
	 * Android Context service tracker
	 */
	private ServiceTracker contextServiceTracker;
	
	/**
	 * service for Android App Context
	 */
	private IAndroidContext androidContextService;
	
	/**
	 * serivce for configuration, got by service tracker
	 */
	private ISystemConf serviceConf;
	
	/**
	 * serivce for datasource, got by service tracker
	 */
	private ISQLite serviceDB;
	
	/**
	 * service for outgoing connection to answer receiver
	 */
	private IConOut serviceOutCon;
	
	/**
	 * query for this service
	 */
	private String query;
	
	/**
	 * result of the query
	 */
	private Cursor queryResult;
	
	/**
	 * calling socket connection
	 */
	private Socket receiver;
	
	/**
	 * member to store error from data source connection
	 */
	private String errorDB = "";
	
	/**
	 * Jsonobject of sended data by client
	 */
	private JSONObject payload;
	
	/**
	 * waiting for database feedback
	 */
	private boolean wait = true;
	
	/**
	 * type of query 
	 * 1 - select
	 * 2 - insert/Update/delete
	 * 3 - multiinsert
	 * 4 - get complete db copy
	 * 9 - no query
	 */
	private int queryType = 9;
	
	/**
	 * identifier of actions after query has been executed
	 * 1 - if set or update don't come with id of dataset -> get last id + 1
	 * 2 - on changes in Gateway database - send burst to spots 
	 */
	private int intraAction = 0;
	
	/** Map to store payload **/
	private Map<String, Object> data;
	
	/** counts inserted rows **/;
	private int resultRows = 0;
	
	/** name of requested table **/
	private String name;
	
	/** command of the incoming request */
	private String command;
	
	/** Android App Context **/
	private Context appContext;
	
	/**identifier if Spot = gateway **/
	private boolean isGate = false;
	
	@Override
	public void run() {
		// getting the configuration-unit-service
		SysConfServiceTracker = new ServiceTracker(this.context, ISystemConf.class.getName(), null );
		SysConfServiceTracker.open();
		
		// getting the datasource service
		serviceTrackerDB = new ServiceTracker(this.context, ISQLite.class.getName(), null );
		serviceTrackerDB.open();
		
		// getting the android context service
		contextServiceTracker = new ServiceTracker(this.context, IAndroidContext.class.getName(), null );
		contextServiceTracker.open();
		
		/** wait for services to come online */
		synchronized (this) {
			while(true){
				serviceConf = (ISystemConf) SysConfServiceTracker.getService();
				if(serviceConf != null){
					break;
				}else{
					try {
						wait(2000);
					} catch (InterruptedException e) {
						Log.e(TAG, "wait for Configuration Service exception: " + e.getMessage());
					}
				}
			}
			
			while(true){
				serviceDB = (ISQLite) serviceTrackerDB.getService();
				if(serviceDB != null){
					break;
				}else{
					try {
						wait(2000);
					} catch (InterruptedException e) {
						Log.e(TAG, "wait for DB Service exception: " + e.getMessage());
					}
				}
			}
			
			while(true){
				androidContextService = (IAndroidContext) contextServiceTracker.getService();
				if(androidContextService != null){
					appContext = androidContextService.transferContext();
					//0 = private mode
					SharedPreferences settings = appContext.getSharedPreferences("hwip", 0);
					isGate = settings.getBoolean("isgateway", false);
					Log.i(TAG, "Is this a gateway? " + isGate);
					break;
				}else{
					try {
						wait(2000);
					} catch (InterruptedException e) {
						Log.e(TAG, "wait for android Context Service exception: " + e.getMessage());
					}
				}
			}
		}
		
		data = new LinkedHashMap<String, Object>();
		try {
			data = JsonToMap(payload);
		} catch (JSONException e) {
			Log.e(TAG, "Json Exception: " + e.getMessage());
		}
		
		performDbRequest();
		showDatainGui();
	}

	/**
	 * sets query for DataBase request and execute them
	 */
	private void performDbRequest(){
		
		//if some devices come without id
		boolean getId = false;
		//get command from data map
		command = (String) data.get("command");
		//set wait true because of intern database requests
		wait = true;
		if(!(command == null)){
			if(intraAction == 0){
				if(command.equals("setnewfloor")){
					if(data.get("id") != null){
						queryType = 2;
						query = serviceConf.getQueryForSetFloor((Integer)data.get("id"), (Integer) data.get("num"), (String) data.get("desc"));
						name = "Set floor";
					}else{
						getId = true;
					}
				}
				
				if(command.equals("setnewroom")){
					if(data.get("id") != null){
						queryType = 2;
						query = serviceConf.getQueryForSetRoom((Integer) data.get("id"), (Integer) data.get("floorid"), (String) data.get("desc"));
						name = "Set room";
					}else{
						getId = true;
					}
				}
				
				if(command.equals("setnewrt")){
					if(data.get("id") != null){
						queryType = 2;
						query = serviceConf.getQueryForSetRadioType((Integer) data.get("id"), (String) data.get("name"));
						name = "Set radio Type";
					}else{
						getId = true;
					}
				}
				
				if(command.equals("setnewdevice")){
					if(data.get("id") != null){

						queryType = 2;
						query = serviceConf.getQueryForSetDevice((Integer) data.get("id"), (Integer) data.get("rtid"), (String) data.get("model"), 
								(String) data.get("manufacture"), (String) data.get("specific"), (String) data.get("response"), getAttrArr());
						name = "Set device";
					}else{
						getId = true;
					}
				}	
				
				if(command.equals("setnewcommand")){
					if(data.get("id") != null){
						queryType = 2;
						query = serviceConf.getQueryForSetCommand((Integer) data.get("id"), (String) data.get("command"), (String) data.get("attr"), 
							(String) data.get("response"));
						name = "Set command";
					}else{
						getId = true;
					}
				}
				
				if(command.equals("setnewcontrolscript")){
					if(data.get("id") != null){
						queryType = 2;
						query = serviceConf.getQueryForSetControlscript((Integer) data.get("id"), (Integer) data.get("comid"), (Integer)data.get("sysdevid"),
								(String) data.get("value"), (String) data.get("time"), (Integer) data.get("nextid"));
						name = "Set control script";
					}else{
						getId = true;
					}
				}
				
				if(command.equals("linkdevicecommand")){
					queryType = 2;
					query = serviceConf.getQueryForSetLinkDevCom((Integer) data.get("devid"), (Integer) data.get("comid"));
					name = "Linked device and command";
				}
				
				if(command.equals("connectdevices")){
					queryType = 2;
					query = serviceConf.getQueryForSetConnectDevices((Integer) data.get("sysdevid"), (Integer)data.get("csid"));
					name = "Connect devices";
				}
							
				if(command.equals("updatefloor")){
					queryType = 2;
					query = serviceConf.getQueryForUpdateFloor((Integer) data.get("id"), (Integer) data.get("num"), (String) data.get("desc"));
					name = "Update floor";
				}
				
				if(command.equals("updateroom")){
					queryType = 2;
					query = serviceConf.getQueryForUpdateRoom((Integer) data.get("id"), (Integer) data.get("floorid"), (String) data.get("desc"));
					name = "Update room";
				}			
				
				if(command.equals("updatert")){
					queryType = 2;
					query = serviceConf.getQueryForUpdateRadioType((Integer) data.get("id"), (String) data.get("name")); 
					name = "Update radio Type";
				}
				
				if(command.equals("updatedevice")){
					queryType = 2;
					query = serviceConf.getQueryForUpdateDevice((Integer) data.get("id"), (Integer) data.get("rtid"), (String) data.get("model"), 
							(String) data.get("manufacture"), (String) data.get("specific"), (String) data.get("response"), getAttrArr());
					name = "Update device";
				}
				
				if(command.equals("updatecommand")){
					queryType = 2;
					query = serviceConf.getQueryForUpdateCommand((Integer) data.get("id"), (String) data.get("command") , 
							(String) data.get("attr"), (String) data.get("response"));
					name = "Update command";
				}
				
				if(command.equals("updatecontrolscript")){
					queryType = 2;
					query = serviceConf.getQueryForUpdateControlscript((Integer) data.get("id"), (Integer) data.get("comid"), (Integer) data.get("sysdevid"), 
							(String) data.get("value"), (String) data.get("time"), (Integer) data.get("nextid"));
					name = "Update control script";
				}
				
				if(command.equals("updatespot")){
					queryType = 2;
					query = serviceConf.getQueryForUpdateSpot((Integer) data.get("id"), (Integer) data.get("roomid"), (String) data.get("ipaddress"));
					name = "Update spot";
				}
				
				if(command.equals("deletefloor")){
					queryType = 2;
					query = serviceConf.getQueryForDeleteFloor((Integer) data.get("id"));
					name = "Delete floor";
				}
	
				if(command.equals("deleteroom")){
					queryType = 2;
					query = serviceConf.getQueryForDeleteRoom((Integer) data.get("id"));
					name = "Delete room";
				}
	
				if(command.equals("deletespot")){
					queryType = 2;
					query = serviceConf.getQueryForDeleteSpot((Integer) data.get("id"));
					name = "Delete spot";
				}
	
				if(command.equals("deletert")){
					queryType = 2;
					query = serviceConf.getQueryForDeleteRadiotype((Integer) data.get("id"));
					name = "Delete radio Type";
				}
	
				if(command.equals("deletedevice")){
					queryType = 2;
					query = serviceConf.getQueryForDeleteDevice((Integer) data.get("id"));
					name = "Delete device";
				}
	
				if(command.equals("deletecommand")){
					queryType = 2;
					query = serviceConf.getQueryForDeleteCommand((Integer) data.get("id"));
					name = "Delete command";
				}
	
				if(command.equals("deletecontrolscript")){
					queryType = 2;
					query = serviceConf.getQueryForDeleteControlScript((Integer) data.get("id"));
					name = "Delete control script";
				}
	
				if(command.equals("unlinkdevicecommand")){
					queryType = 2;
					query = serviceConf.getQueryForDeleteLinkDevCom((Integer) data.get("devid"), (Integer) data.get("comid"));
					name ="unlink device and command";
				}
				
				if(command.equals("unconnectdevices")){
					queryType = 2;
					query = serviceConf.getQueryForDeleteConnDevs((Integer) data.get("sysdevid"), (Integer) data.get("csid"));
					name = "Delete disconnect devices";
				}
				
				if(command.equals("deletesysdev")){
					queryType = 2;
					query = serviceConf.getQueryForDeleteSysDevice((Integer) data.get("id"));
					name = "Delete system Device";
				}
				
				if(command.equals("getroomforfloor")){
					queryType = 1;
					query = serviceConf.getQueryForRoomsByFk((Integer) data.get("floorid"));
					name = "Rooms";
				}
	
				if(command.equals("getspotforroom")){
					queryType = 1;
					query = serviceConf.getQueryForSpotsByFk((Integer) data.get("roomid"));
					name = "Spots";
				}
	
				if(command.equals("getdeviceforrt")){
					queryType = 1;
					query = serviceConf.getQueryForDevicesByFk((Integer) data.get("rtid"));
					name = "Devices";
				}
	
				if(command.equals("getsysdevfordevice")){
					queryType = 1;
					query = serviceConf.getQueryForSysDevByDevFk((Integer) data.get("devid"));
					name = "System devices";
				}
	
				if(command.equals("getsysdevforspot")){
					queryType = 1;
					query = serviceConf.getQueryForSysDevBySpotFk((Integer) data.get("spotid"));
					name = "System devices";
				}
	
				if(command.equals("getcommandfordevice")){
					queryType = 1;
					query = serviceConf.getQueryForCommandByDevFk((Integer) data.get("devid"));
					name = "Commands";
				}
	
				if(command.equals("getcontrolfordevice")){
					queryType = 1;
					query = serviceConf.getQueryForControlBySysDevFk((Integer) data.get("sysdevid"));	
					name = "Control scripts";
				}
	
				if(command.equals("getfloors")){
					queryType = 1;
					query = serviceConf.getQueryForFloors();
					name = "Floors";
				}
	
				if(command.equals("getrooms")){
					queryType = 1;
					query = serviceConf.getQueryForRooms();
					name = "Rooms";
				}
	
				if(command.equals("getspots")){
					queryType = 1;
					query = serviceConf.getQueryForSpots();
					name = "Spots";
				}
	
				if(command.equals("getrts")){
					queryType = 1;
					query = serviceConf.getQueryForRadiotypes();
					name = "Radio Types";
				}
	
				if(command.equals("getdevices")){
					queryType = 1;
					query = serviceConf.getQueryForDevices();
					name = "Devices";
				}
	
				if(command.equals("getcommands")){
					queryType = 1;
					query = serviceConf.getQueryForCommands();
					name = "Commands";
				}
	
				if(command.equals("getsysdevs")){
					queryType = 1;
					query = serviceConf.getQueryForSysDevices();
					name = "System devices";
				}
	
				if(command.equals("getcontrolscripts")){
					queryType = 1;
					query = serviceConf.getQueryForControlscripts();
					name = "Control scripts";
				}
				
				if(command.equals("changegateway")){
					queryType = 9;
					if(data.get("ip") != null){
						setGatewayPref(data.get("ip").toString());
					}
				}
				
				if(getId){
					queryType = 1;
					intraAction = 1;
					if(command.equals("setnewfloor")){
						query = serviceConf.getQueryForFloors();
					}
					
					if(command.equals("setnewroom")){
						query = serviceConf.getQueryForRooms();
					}
					
					if(command.equals("setnewrt")){
						query = serviceConf.getQueryForRadiotypes();
					}
					
					if(command.equals("setnewdevice")){
						query = serviceConf.getQueryForDevices();
					}
					
					if(command.equals("setnewcommand")){
						query = serviceConf.getQueryForCommands();
					}
					
					if(command.equals("setnewcontrolscript")){
						query = serviceConf.getQueryForControlscripts();
					}
				}
				if(queryType == 2){
					intraAction = 2;
				}
			}else if(intraAction == 2 && queryType == 1){
				query = serviceConf.getQueryForSpots();
			}
		}
		
		
		serviceDB.transferData(this);
		/*
		 * Synchronizing for the purpose that this thread can wait until
		 * the DB thread is finished. If this is the case the DB thread
		 * calls the synchronized <code>stopWaiting()</code> method and so
		 * becomes the monitor of this thread. The thread will get notified
		 * and stops waiting.
		 */
		synchronized (this) 
		{
			while(wait)
			{
				try 
				{
					Log.i(TAG, "Waiting for Db to finish");
					this.wait();
				} 
				catch (InterruptedException e) 
				{
					errorDB += "Could not execute request!";
					Log.e(TAG, "Syncerror: " + e.getMessage());
				}
			}
		}
		
		Log.i(TAG, "Data received from DB");
		// generate answer to receiver
		answerReceiver();
	}
	
	@Override
	public void answerReceiver() {
		// getting the configuration-unit-service
		serviceTrackerOutCon = new ServiceTracker(this.context, IConOut.class.getName(), null );
		serviceTrackerOutCon.open();
		synchronized (this) {
			while(true){
				serviceOutCon = (IConOut) serviceTrackerOutCon.getService();
				if(serviceOutCon != null){
					break;
				}else{
					try {
						wait(2000);
					} catch (InterruptedException e) {
						Log.e(TAG, "wait for Outgoing Connection Service exception: " + e.getMessage());
					}
				}
			}
		}
		
		JSONObject answer = generateAnswer();
		
		Log.i(TAG, "Send System Config Data to: " + receiver);
		serviceOutCon.startCon(receiver, this.context, answer, securityFlag);
		if(intraAction == 2 && queryType == 2){
			queryType = 1;
			performDbRequest();
		}

	}
	
	@Override
	public JSONObject generateAnswer() {
		JSONObject answer = new JSONObject();
		System.out.println("intraaction: " + intraAction + " querytype: " + queryType + " isGate: " + isGate);
		try{
			if(errorDB == ""){
				if(intraAction == 1){
					intraAction = 0;
					putId();
					performDbRequest();
					return null;
				}else if(intraAction == 2 && queryType == 1 && isGate){
					Log.i(TAG, "send Burst about database changes.");
					intraAction = 0;
					sendBurst();
					return null;
				}else if(resultRows > 0){
					answer.put("name", name);
					answer.put("rows", resultRows);
					
				}else if(queryResult.getCount() > 0){
					Map<Integer, Object> table = new LinkedHashMap<Integer, Object>();
					List<String> column = new ArrayList<String>();
					int rowcount = 0;
					while(queryResult.moveToNext()){
						Map<Integer, Object> row = new LinkedHashMap<Integer, Object>();
						for(int i = 0; i < queryResult.getColumnCount(); i++){
							row.put(i, queryResult.getString(i));
							if(rowcount == 0){
								column.add(queryResult.getColumnName(i));
							}
						}
						rowcount++;
						table.put(rowcount, row);
					}
					
					answer.put("name" , name);
					answer.put("columns", column);
					answer.put("table", table);
				}else if(queryResult.getCount() == 0 ){
					answer.put("name", name);
					answer.put("error", "no entrys");
				}
			}else{
				Log.w(TAG, "ErrorDb occured");
				answer.put("name", name);
				answer.put("error", "Database failed");
			}
		}catch (JSONException e){
			Log.e(TAG, "Json exception at generating answer: " + e.getMessage());
		}
		
		return answer;
	}
	
	/** puts the last id + 1 to data map */ 
	public void putId(){
		if(queryResult.moveToLast())
			data.put("id", (queryResult.getInt(0) + 1));
	}
	
	/** transform a JsonObject to a Map **/
	@SuppressWarnings({ "rawtypes"})
	private Map<String, Object> JsonToMap(JSONObject object) throws JSONException {
		Map<String, Object> map = new LinkedHashMap<String, Object>();
		Iterator keys = object.keys();
		while (keys.hasNext()) {
			String key = (String) keys.next();
			map.put(key, object.get(key));
		}
		return map;
	}
	
	/**sends Database changes to other spots */
	private void sendBurst(){
		JSONObject sendData = new JSONObject(data);
		int count = queryResult.getCount();
		String target[] = new String[count];
		int i = 0;
		if(count > 0){
			queryResult.moveToFirst();
			while(queryResult.moveToNext()){
				target[i] = queryResult.getString(2);
				System.out.println(target[i]);
				i++;
			}
			serviceOutCon.startBurstCon(target, context, sendData, securityFlag);
		}
	}

	/** method to send incoming data to admin console view */
	private void showDatainGui(){
    	Set<String> keyset =  data.keySet();
    	String showmessage = "\n";
    	for(String s : keyset){
    		showmessage += "\n" + s + ":\n" + data.get(s);
    	}
    	
    	androidContextService.transferData(showmessage);
    }
	
	/**
	 * transforms attribute array to Strign array
	 * @param context
	 */
	private String[] getAttrArr(){
		String[] attr;
		if(data.get("attr") != null){
			if(data.get("attr").getClass() == String.class)
			{
				attr = new String[]{(String) data.get("attr")};
			}else if(data.get("attr").getClass() == JSONArray.class) {
				JSONArray jarr = (JSONArray) data.get("attr");
				attr = new String[jarr.length()];
				for(int i = 0 ; i < jarr.length() ; i++){
					try {
						attr[i] = jarr.getString(i);
					} catch (JSONException e) {
						Log.e(TAG, "Jsonexception @ setnewdevice: " + e.getMessage());
					}
				}
			}else{
				attr = (String[]) data.get("attr");
			}
			
		}else
			attr = new String[0];
		
		return attr;
	}
	
	/**
	 * Sets Spot and Gatewayspecification 
	 * @param ipAddress : ip-adress of Gateway
	 */
    private void setGatewayPref(String ipAddress){
    	SharedPreferences settings = appContext.getSharedPreferences("hwip", Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = settings.edit();
        if(ipAddress != null){
        	editor.putString("gatewayip", ipAddress);
        }
        editor.commit();
    }
    
	@Override
	public void setBundleContext(BundleContext context) {
		this.context = context;
	}

	@Override
	public void setConnection(Socket connection) {
		this.receiver = connection;
	}

	@Override
	public void setPayload(JSONObject payload) {
		this.payload = payload;
	}

	@Override
	public void setSecurityFlag(boolean flag) {
		this.securityFlag = flag;
	}

	@Override
	public synchronized void stopWaiting() {
		this.wait = false;
		this.notify();
	}

	@Override
	public String getQuery() {
		return query;
	}

	@Override
	public int getQueryType() {
		return queryType;
	}

	@Override
	public void setErrorResult(String error) {
		this.errorDB = error;
	}

	@Override
	public void setQueryResult(Cursor result) {
		this.queryResult = result;
	}

	@Override
	public void setQueryResult(int rows) {
		this.resultRows = rows;
	}

	@Override
	public Map<String, Object> getCompleteDB() {
		// not used
		return null;
	}

	@Override
	public void setCompleteDbResult(JSONObject arg0) {
		//not used
	}
}
